﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
using WeDonekRpc.CacheClient.Config;
using WeDonekRpc.Helper;

namespace WeDonekRpc.CacheClient.Redis
{
    internal class SyncRedisHelper : RedisHelper
    {
        public static bool IsExitKey (IDatabase client, string key, string colName)
        {
            return client.HashExistsAsync(key, colName).Result;
        }
        public static bool IsExitKey (IDatabase client, string key)
        {
            return client.KeyExistsAsync(key).Result;
        }
        public static bool SetExpire (IDatabase client, string key, DateTime time)
        {
            return client.KeyExpireAsync(key, time).Result;
        }
        public static T[] Gets<T> (IDatabase client, string[] keys)
        {
            IBatch batch = client.CreateBatch();
            Task<RedisValue>[] vals = keys.ConvertAll(c =>
            {
                return batch.StringGetAsync(c);
            });
            batch.Execute();
            return vals.ConvertAll(a =>
            {
                RedisValue res = a.Result;
                if (!res.HasValue || res.IsNull)
                {
                    return default;
                }
                return RedisTools.GetResult<T>(res);
            });
        }
        public static Dictionary<string, T> GetKeyValue<T> (IDatabase client, string[] keys)
        {
            IBatch batch = client.CreateBatch();
            KeyValuePair<string, Task<RedisValue>>[] vals = keys.ConvertAll(c =>
            {
                return new KeyValuePair<string, Task<RedisValue>>(c, batch.StringGetAsync(c));
            });
            batch.Execute();
            return vals.ToDictionary(a => a.Key, a =>
            {
                RedisValue res = a.Value.Result;
                if (!res.HasValue || res.IsNull)
                {
                    return default;
                }
                return RedisTools.GetResult<T>(res);
            });
        }
        #region Strings
        public static double Increment (IDatabase client, string key, double num)
        {
            return client.StringIncrementAsync(key, num).Result;
        }
        public static long Increment (IDatabase client, string key, long num)
        {
            return client.StringIncrementAsync(key, num).Result;
        }
        public static double Decrement (IDatabase client, string key, double num)
        {
            return client.StringDecrementAsync(key, num).Result;
        }
        public static long Decrement (IDatabase client, string key, long num)
        {
            return client.StringDecrementAsync(key, num).Result;
        }

        public static Result ExecLua<T, Result> (IDatabase client, string lua, T arg) where T : class
        {
            LuaScript script = LuaScript.Prepare(lua);
            RedisResult result = client.ScriptEvaluate(script, arg);
            if (result.IsNull)
            {
                return default;
            }
            else if (result.Type == ResultType.Error)
            {
                throw new ErrorException("cache.redis.lua.exec.fail")
                {
                    Args = new Dictionary<string, string>
                    {
                        {"error",result.ToString() },
                        {"arg",arg.ToJson() },
                        {"lua",lua }
                    }
                };
            }
            return RedisTools.GetLuaResult<Result>(result);
        }

        public static void ExecLua (IDatabase client, byte[] lua, RedisKey[] keys, RedisValue[] arg)
        {
            RedisResult result = client.ScriptEvaluate(lua, keys, arg);
            if (result.Type == ResultType.Error)
            {
                throw new ErrorException("cache.redis.lua.exec.fail")
                {
                    Args = new Dictionary<string, string>
                    {
                        {"error",result.ToString() },
                         {"keys",keys==null?string.Empty:keys.Join('|') },
                        {"values",arg==null?string.Empty:arg.Join('|') },
                        {"lua",Encoding.UTF8.GetString(lua) }
                    }
                };
            }
        }
        public static Result ExecLua<Result> (IDatabase client, string lua, RedisKey[] keys, RedisValue[] arg)
        {
            RedisResult result = client.ScriptEvaluate(lua, keys, arg);
            if (result.IsNull)
            {
                return default;
            }
            else if (result.Type == ResultType.Error)
            {
                throw new ErrorException("cache.redis.lua.exec.fail")
                {
                    Args = new Dictionary<string, string>
                    {
                        {"error",result.ToString() },
                         {"keys",keys==null?string.Empty:keys.Join('|') },
                        {"values",arg==null?string.Empty:arg.Join('|') },
                        {"lua",lua }
                    }
                };
            }
            return RedisTools.GetLuaResult<Result>(result);
        }
        public static Result ExecLua<Result> (IDatabase client, byte[] lua, RedisKey[] keys, RedisValue[] arg)
        {
            RedisResult result = client.ScriptEvaluate(lua, keys, arg);
            if (result.IsNull)
            {
                return default;
            }
            else if (result.Type == ResultType.Error)
            {
                throw new ErrorException("cache.redis.lua.exec.fail")
                {
                    Args = new Dictionary<string, string>
                    {
                        {"error",result.ToString() },
                         {"keys",keys==null?string.Empty:keys.Join('|') },
                        {"values",arg==null?string.Empty:arg.Join('|') },
                          {"lua",Encoding.UTF8.GetString(lua) }
                    }
                };
            }
            return RedisTools.GetLuaResult<Result>(result);
        }
        public static bool TryGet<T> (IDatabase client, string key, out T data)
        {
            RedisValue value = client.StringGetAsync(key).Result;
            return RedisTools.GetT(value, out data);
        }
        public static T Get<T> (IDatabase client, string key)
        {
            RedisValue res = client.StringGetAsync(key).Result;
            if (!res.HasValue || res.IsNull)
            {
                return default;
            }
            return RedisTools.GetResult<T>(res);
        }
        public static bool Set<T> (IDatabase client, string key, T data, TimeSpan? expires)
        {
            expires = CacheTimeConfig.FormatCacheTime(key, expires);
            return client.StringSetAsync(key, RedisTools.Serializable<T>(data), expires).Result;
        }
        public static bool Set<T> (IDatabase client, string key, T data, DateTime expires)
        {
            return client.StringSetAsync(key, RedisTools.Serializable<T>(data), expires - DateTime.Now).Result;
        }
        public static bool Remove (IDatabase client, string key)
        {
            return client.KeyDeleteAsync(key).Result;
        }
        public static long Remove (IDatabase client, string[] key)
        {
            return client.KeyDeleteAsync(key.Cast<RedisKey>().ToArray()).Result;
        }
        public static bool Replace<T> (IDatabase client, string key, T data, TimeSpan? expires)
        {
            expires = CacheTimeConfig.FormatCacheTime(key, expires);
            return client.StringSetAsync(key, RedisTools.Serializable<T>(data), expires, When.Exists, CommandFlags.None).Result;
        }
        public static bool Add<T> (IDatabase client, string key, T data, TimeSpan? expires)
        {
            expires = CacheTimeConfig.FormatCacheTime(key, expires);
            return client.StringSetAsync(key, RedisTools.Serializable<T>(data), expires, When.NotExists, CommandFlags.None).Result;
        }
        #endregion
        public static string[] FindKey (IDatabase client, string pattern)
        {
            RedisResult result = client.ScriptEvaluate(LuaScript.Prepare("local res=redis.call('KEYS', @keypattern) return res"), new { @keypattern = pattern });
            return result.IsNull ? Array.Empty<string>() : (string[])result;
        }
        #region Hash

        public static double Increment (IDatabase client, string key, string colname, double num)
        {
            return client.HashIncrementAsync(key, colname, num).Result;
        }
        public static long Increment (IDatabase client, string key, string colname, long num)
        {
            return client.HashIncrementAsync(key, colname, num).Result;
        }
        public static double Decrement (IDatabase client, string key, string colname, double num)
        {
            return client.HashDecrementAsync(key, colname, num).Result;
        }
        public static long Decrement (IDatabase client, string key, string colname, long num)
        {
            return client.HashDecrementAsync(key, colname, num).Result;
        }
        public static bool Add<T> (IDatabase client, string key, string colname, T data)
        {
            return client.HashSetAsync(key, colname, RedisTools.Serializable<T>(data), When.NotExists).Result;
        }
        public static bool Set<T> (IDatabase client, string key, string colname, T data)
        {
            return client.HashSetAsync(key, colname, RedisTools.Serializable<T>(data)).Result;
        }
        public static bool Replace<T> (IDatabase client, string key, string colname, T data)
        {
            return client.HashSetAsync(key, colname, RedisTools.Serializable<T>(data), When.Exists).Result;
        }
        public static bool Remove (IDatabase client, string key, string colname)
        {
            return client.HashDeleteAsync(key, colname).Result;
        }

        public static bool TryGet<T> (IDatabase client, string key, string colname, out T data)
        {
            RedisValue value = client.HashGetAsync(key, colname).Result;
            return RedisTools.GetT(value, out data);
        }
        public static bool TryGet<T> (IDatabase client, string key, out T[] data)
        {
            RedisValue[] value = client.HashValuesAsync(key).Result;
            return RedisTools.GetT<T>(value, out data);
        }
        public static bool TryGetColName (IDatabase client, string key, out string[] colname)
        {
            RedisValue[] value = client.HashKeysAsync(key).Result;
            return RedisTools.GetT<string>(value, out colname);
        }
        public static long GetCount (IDatabase client, string key)
        {
            return client.HashLengthAsync(key).Result;
        }
        public static long Remove (IDatabase client, string key, string[] colname)
        {
            return client.HashDeleteAsync(key, colname.Cast<RedisValue>().ToArray()).Result;
        }
        #endregion

        #region SyncList
        public static bool GetList<T> (IDatabase client, string key, int index, out T data)
        {
            RedisValue value = client.ListGetByIndexAsync(key, index).Result;
            return RedisTools.GetT<T>(value, out data);
        }
        public static bool GetList<T> (IDatabase client, string key, out T[] data)
        {
            RedisValue[] value = client.ListRangeAsync(key).Result;
            return RedisTools.GetT<T>(value, out data);
        }
        public static bool GetList<T> (IDatabase client, string key, int index, int size, out T[] data)
        {
            RedisValue[] value = client.ListRangeAsync(key, ( index - 1 ) * size, index * size).Result;
            return RedisTools.GetT<T>(value, out data);
        }
        public static async void SetList<T> (IDatabase client, string key, int index, T data)
        {
            await client.ListSetByIndexAsync(key, index, RedisTools.Serializable<T>(data));
        }
        public static long ListAddLeft<T> (IDatabase client, string key, T data)
        {
            return client.ListLeftPushAsync(key, RedisTools.Serializable<T>(data)).Result;
        }
        public static long ListAddRight<T> (IDatabase client, string key, T data)
        {
            return client.ListRightPushAsync(key, RedisTools.Serializable<T>(data)).Result;
        }
        public static long ListAddLeft<T> (IDatabase client, string key, T[] data)
        {
            return client.ListLeftPushAsync(key, Array.ConvertAll<T, RedisValue>(data, a => RedisTools.Serializable<T>(a))).Result;
        }
        public static bool ListLeftPop<T> (IDatabase client, string key, out T data)
        {
            RedisValue value = client.ListLeftPop(key);
            return RedisTools.GetT<T>(value, out data);
        }
        public static bool ListLeftPop<T> (IDatabase client, string key, int count, out T[] data)
        {
            RedisValue[] value = client.ListLeftPop(key, count);
            return RedisTools.GetT<T>(value, out data);
        }
        public static bool ListRightPop<T> (IDatabase client, string key, out T data)
        {
            RedisValue value = client.ListRightPop(key);
            return RedisTools.GetT<T>(value, out data);
        }
        public static long ListAddRight<T> (IDatabase client, string key, T[] data)
        {
            return client.ListRightPushAsync(key, Array.ConvertAll<T, RedisValue>(data, a => RedisTools.Serializable<T>(a))).Result;
        }
        public static long GetListCount (IDatabase client, string key)
        {
            return client.ListLengthAsync(key).Result;
        }
        public static async void ListTop (IDatabase client, string key, int top)
        {
            await client.ListTrimAsync(key, 0, top);
        }
        public static long ListRemove<T> (IDatabase client, string key, T data)
        {
            return client.ListRemoveAsync(key, RedisTools.Serializable<T>(data)).Result;
        }
        public static async void ListTop (IDatabase client, string key, int start, int end)
        {
            await client.ListTrimAsync(key, start, end);
        }
        #endregion

    }
}
